package io.github.betwowt.gateway.route;

import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.core.env.Environment;
import reactor.core.publisher.Flux;

import java.net.URI;
import java.util.Collections;
import java.util.Set;

import static org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory.PATTERN_KEY;
import static org.springframework.cloud.gateway.support.NameUtils.normalizeFilterFactoryName;
import static org.springframework.cloud.gateway.support.NameUtils.normalizeRoutePredicateName;

/**
 * @author betwowt
 */
public class NamespaceRouteDefinitionLocator implements RouteDefinitionLocator {

    public static final String MS_IS = "service.includes";
    public static final String MS_NS = "service.namespace";

    private String routeIdPrefix = "g2_";
    private final Binder binder;

    public NamespaceRouteDefinitionLocator(Environment env) {
        this.binder = Binder.get(env);
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        String namespace = this.binder.bind(MS_NS, Bindable.of(String.class)).orElse("");
        Set<String> services =
                this.binder.bind(MS_IS, Bindable.setOf(String.class)).orElseGet(Collections::emptySet);

        return Flux.fromIterable(services).map(serviceId -> {
            RouteDefinition routeDefinition = new RouteDefinition();
            routeDefinition.setId(this.routeIdPrefix + serviceId);
            routeDefinition.setUri(URI.create("lb://" + namespace + serviceId));

            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
            predicate.addArg(PATTERN_KEY, "/services/" + serviceId + "/**");

            routeDefinition.getPredicates().add(predicate);

            {
                FilterDefinition filter = new FilterDefinition();
                filter.setName(normalizeFilterFactoryName(RetryGatewayFilterFactory.class));
                filter.addArg("retries", "1");
                routeDefinition.getFilters().add(filter);
            }
            {
                FilterDefinition filter = new FilterDefinition();
                filter.setName(normalizeFilterFactoryName(StripPrefixGatewayFilterFactory.class));
                filter.addArg("parts", "2");
                routeDefinition.getFilters().add(filter);
            }

            return routeDefinition;
        });
    }
}
